/*! @file menusys.h
    @version 1.5
    @brief menusys public interface
 */
/*! @mainpage

    <table>
        <tr><th>Library     <td>menusys
        <tr><th>Author      <td>Brodie Thiesfield [code at jellycan dot com]
        <tr><th>Source      <td>http://code.jellycan.com/menusys/
    </table>

    An interactive cross-platform command-line shell. Menusys facilitates the creation
    of programs that provide a scriptable command-line for user interaction. Standard
    command line functionality is provided by this library, for instance, command help,
    options, file handling, scripting, timing, and loops. Only the commands for your
    own functionality (e.g. stress test clients, C or C++ API testing) needs to be
    implemented.

    @section features FEATURES

    -   MIT Licence allows free use in all software (including GPL and commercial)
    -   multi-platform (Windows 95/98/ME/NT/2K/XP, Linux, Unix)
    -   full command line parsing into commands and arguments
    -   summary and extended help for internal and user commands
    -   MBCS and UNICODE input/output (current locale and UTF-8)
    -   generic file system commands (e.g. cd, pwd, shell)
    -   timing and timing analysis (multiple timers, lap timers)
    -   multiple threads (start, stop, join)
    -   simple procedures and loops
    -   supports both MBCS and UNICODE build types on Windows
    -   compiles cleanly at warning level 4 (Windows/VC.NET 2003) and -Wall (Linux/gcc)

    @section usage USAGE

    The following steps are required to use menusys. The file main.cpp included
    with the menusys distribution implements the minimal menusys-based program.

    <ol>
    <li> Include the menusys.h header file. It is recommended to also include the
         \<locale.h\> header and call setlocale() in main().

<pre>\#include \<locale.h\>
\#include "menusys.h"</pre>

   <li> Provide implementations for the functions MSYS_ThreadUserCreate(),
        MSYS_ThreadUserDestroy(), and MSYS_GetPrompt().

   <li> Define an array of @link MSYS_DispatchEntry @endlink entries as #MSYS_Commands
        This is the command definition and help text for each command implemented by you.
        The internal menusys commands will be automatically added to this list.

   <li> Implement the main() function and call MSYS_Process().

<pre>int
_tmain(
    int     argc,
    TCHAR * argv[] )
{
    setlocale(LC_ALL, "");
    fprintf(stdout, "MENUSYS\n\n");
    const TCHAR * pInputFile = (argc > 1) ? argv[1] : NULL;
    return MSYS_Process(pInputFile, true) ? 0 : 1;
}</pre>

    </ol>

    @section notes NOTES

    Menusys compiles with thread support by default (native threads on Windows,
    pthreads on all other platforms). This can be enabled or disabled by the
    client in the call to MSYS_Process() however the thread libraries are still
    required. To disable all threads so that the thread library doesn't need to
    be linked, define MSYS_NOTHREADS when compiling the library.

    @section licence MIT LICENCE

    The licence text below is the boilerplate "MIT Licence" used from:
    http://www.opensource.org/licenses/mit-license.php

    Copyright (c) 2007, Brodie Thiesfield

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is furnished
    to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
    FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
    COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
    IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#ifndef INCLUDED_menusys
#define INCLUDED_menusys

#include <vector>

/*! @def TCHAR

    @brief Character type that depends on the _UNICODE definition. Defined as char
    for non-Unicode builds and wchar_t (Windows) or UChar (Unix) for Unicode builds.
    The use of ICU as the backing implementation can be forced on Windows by
    defining USING_ICU.

    On Windows the Unicode is supported natively and works interchangeably
    with ICU (as on Windows UChar == wchar_t). Unicode interfaces are supported
    on Unix only through the use of ICU.
 */
#ifdef _WIN32
# include <windows.h>
# ifndef USING_ICU
#  include <tchar.h>
# endif
#else
# ifdef _UNICODE
#  include <unicode/utypes.h>   // ICU
#  define TCHAR  UChar
# else
#  define TCHAR  char
# endif
#endif

/*! @brief Menu command handler arguments.

    Arguments to every command handler will be parsed out of the command
    line and supplied to the command handler in this array.
*/
typedef std::vector<TCHAR*> MSYS_Args;

/*! @brief Thread definition */
struct MSYS_Thread
{
    MSYS_Thread() : mUserData(NULL), mThreadId(-1) { }

    /*! @brief Return the current thread ID. */
    inline int GetThreadId() const { return mThreadId; }

    /*! @brief Return the command used to access the current handler.
        This is a pointer to the cmd field of the appropriate
        MSYS_Dispatch entry.
     */
    inline const TCHAR * GetCurrCommand() const { return mCurrCommand; }

    /*! @brief Root application thread always uses this thread ID. */
    const static int ROOT_THREAD = 0;

    /*! @brief User managed thread data.

        It is the responsibility of the user to initialize and uninitialize
        this pointer using the MSYS_ThreadUserCreate() and
        MSYS_ThreadUserDestroy() callbacks.
    */
    void * mUserData;

protected:
    int             mThreadId;
    const TCHAR *   mCurrCommand;
};

/*! @brief Menu command handler function.

    Every menu command is implemented using one of these handlers. The
    command is looked up in the dispatch table (see MSYS_Commands) and
    called passing it the supplied parameters. Errors should be
    notified to the user via display text.

    Handlers should be implemented similar to:

<pre>extern void
ThisHandlerFunction(
    MSYS_Thread *   aThread,
    MSYS_Args &     aArgs
    )
{
    if (MSYS_NeedHelp(aThread, ThisHandlerFunction, aArgs)) {
        Display extra detailed help that is not included in the longHelp entry
        return;
    }

    if (aArgs are invalid) {
        MSYS_Display(aThread, _T("Invalid arguments. See help for details.\n"));
        return;
    }

    general implementation
}</pre>

    For more powerful command argument parsing,
    <a href="http://code.jellycan.com/simpleopt/">SimpleOpt</a> can be used with the
    @c aArgs array. This allows simple parsing of complex command lines into options,
    arguments and files. See the SimpleOpt
    <a href="http://code.jellycan.com/simpleopt-doc/html/index.html">documentation</a> for
    details.

<pre>CSimpleOpt args(aArgs.size(), &amp;*aArgs.begin(), rgOptions, SO_O_USEALL);
while (args.Next()) {
    ...
}</pre>

    @param aThread      Thread requesting the prompt. If threads have not been
                        enabled, this will always be the main thread.

    @param aArgs        Arguments supplied to the command.
*/
typedef void (*MSYS_Handler)(MSYS_Thread * aThread, MSYS_Args & aArgs);

/*! @brief Menu command definition.

    All commands available via the MSYS menu are defined with in a table
    of these entries (see MSYS_Commands).

    @param cmd          Command text typed by the user to start the command.
                        For example, "dir", "list". This must not be NULL.
    @param fn           Function implementing the command. This must not be NULL.
    @param flags        0   Default
                        
                        MSYS_UNQUOTE
                        Quoted characters in the parameter list should be unquoted
                        prior to being passed to the function. Quoted entries
                        like \\n \\t \\r will be converted to the actual characters
                        if this is set to true. If not set, the actual text
                        will be returned. Double quotes are always interpreted as
                        delimiters of parameters and need to be quoted \\" in order
                        to be passed to the command function.

                        MSYS_HIDDEN
                        Don't show this command in the help. All hidden entries with
                        the same function as ones that are being displayed in help
                        should come first in the command array.

    @param oneHelp      One line summary help that will be displayed by the general
                        "help" request. This should not exceed about 60 characters.
    @param longHelp     Extended help text that will be displayed by "help \<command\>"
                        or "\<command\> -?". Help text will be automatically wrapped
                        at 80 characters.
*/
struct MSYS_DispatchEntry {
    const TCHAR *   cmd;
    MSYS_Handler    fn;
    int             flags;
    const TCHAR *   oneHelp;
    const TCHAR *   longHelp;

    bool operator==(const MSYS_DispatchEntry & rhs) const;
};

enum { 
    MSYS_UNQUOTE = 1, 
    MSYS_HIDDEN  = 2 
};

/*! @brief Create a header entry in the short help.

    Use this as the command name of an entry which should be a header in the
    short help display. The entry must look like:

        { MSYS_HEADER, NULL, false, _T("Header Text"), NULL },
*/
extern const TCHAR * MSYS_HEADER;

/*! @brief User supplied function table.

    This table defines the functions that are being exported from the user
    library. Command names and command help will be displayed in the same order
    as they appear in this table. Help will always be supplied for internal
    command first, and then the user commands as defined in this table.

    Multiple entries may use the same command handler function, however they
    should be grouped together in the table, with oneHelp and longHelp provided
    for the last entry only. All other entries must have the help set to NULL.
    For example:

<pre>{ _T("x"),    Exit, false, NULL, NULL },
{ _T("exit"), Exit, false, _T("Exit the application."), NULL },</pre>

    Options that can be accessed via the internal "setopt" command should be
    created as a command with "_setopt_" prefixing the name of the option.
    For example:

<pre>{ _T("_setopt_OptionName"), ThisOptionHandler, false, _T("option help"), NULL },</pre>

    An option command handler will always be called with the following arguments:

    <table>
        <tr><th>Index   <th>Value
        <tr><td>0       <td>"getopt" or "setopt"
        <tr><td>1       <td>option name (the name following the _setopt_ prefix)
        <tr><td>2       <td>value to be set (only with setopt)
    </table>

    An example implementation of an option handler:

<pre>extern void
ThisOptionHandler(
    MSYS_Thread *   aThread,
    MSYS_Args &     aArgs
    )
{
    if (MSYS_NeedHelp(aThread, ThisOptionHandler, aArgs)) return;

    const TCHAR * pAction = aArgs[0];
    const TCHAR * pOption = aArgs[1];
    const TCHAR * pValue  = aArgs.size() \> 2 ? aArgs[2] : NULL;

    if (*pAction == _T('s')) {
        set "pOption" to "pValue"
    }
    else {
        MSYS_DisplayOption(aThread, pOption, "current value");
    }
}</pre>

    The table MUST end with a NULL entry like:

<pre>{ NULL, NULL, false, NULL, NULL }</pre>
*/
extern const MSYS_DispatchEntry MSYS_Commands[];

/*! @brief User supplied default configuration file.

    This is the filename used to load and save options by default when "setopt -load"
    or "setopt -save" is used without specifying a filename. This file will also be
    loaded by default if it exists when when MSYS_Process() is first called.

    An example definition would be:

<pre>\#ifdef _WIN32
extern const TCHAR  MSYS_DefaultOptionsFile[] = _T("menusys.cfg");
\#else
extern const TCHAR  MSYS_DefaultOptionsFile[] = _T(".menusys");
\#endif</pre>

 */
extern const TCHAR MSYS_DefaultOptionsFile[];

/*! @brief Start the menu system.

    Calling this function starts processing of the console or file input
    and exits only when the input file has been completed, or the exit
    command has been encountered.

    @note The locale should be set correctly before displaying the menu.
    It is recommended that setlocale(LC_ALL, ""); be executed before
    calling this function.

    @param aInputFile       Input file that should be processed. MSYS will
                            return immediately following processing the
                            end of this file (no console input). If NULL,
                            the input will be taken from the console.
    @param aEnableThreads   Should multiple threads be permitted?

    @retval true    Successful completion
    @retval false   aInputFile could not be executed
*/
extern bool
MSYS_Process(
    const TCHAR *   aInputFile,
    bool            aEnableThreads
    );

/*! @brief Initialization of thread-specific data structures callback.

    This function is called from MSYS into the user library. It will be
    called once per thread at creation time so that the user can do any
    per-thread initialization required. If per-thread data is required,
    it should be stored in the mUserData data structure. Even if threads
    have not been enabled it will be called once for the main thread.

    @note Do not use call any MSYS_* function calls from this callback.

    A minimal implementation of this function for a program that does
    not use any thread-locale storage is:

<pre>extern void
MSYS_ThreadUserCreate(
    MSYS_Thread *       aNewThread,
    const MSYS_Thread * aParentThread
    )
{
    (void)aNewThread;
    (void)aParentThread;
}</pre>

    @param aNewThread       Thread to be initialized.
    @param aParentThread    Parent thread data. This data should be used to copy
                            all thread-local properties into the new thread
                            properties.
*/
extern void
MSYS_ThreadUserCreate(
    MSYS_Thread *       aNewThread,
    const MSYS_Thread * aParentThread
    );

/*! @brief Uninitialization of thread-specific data structures callback.

    This function is called from MSYS into the user library. It will be
    called once per thread at destruction time so that the user can do any
    per-thread uninitialization required. If per-thread data is required,
    it should be stored in the mUserData data structure. Even if threads
    have not been enabled it will be called once for the main thread.

    @note Do not use call any MSYS_* function calls from this callback.

    A minimal implementation of this function for a program that does
    not use any thread-locale storage is:

<pre>extern void
MSYS_ThreadUserDestroy(
    MSYS_Thread * aThread
    )
{
    (void)aThread;
}</pre>

    @param aThread      Thread to be uninitialized.
*/
extern void
MSYS_ThreadUserDestroy(
    MSYS_Thread * aThread
    );

/*! @brief Request for current prompt callback.

    This function is called from MSYS immediately prior to accepting input
    from the user. It will be called even when input is being read from a
    file.

    @note Do not use call any MSYS_* function calls from this callback.

    @param aThread      Thread requesting the prompt. If threads have not been
                        enabled, this will always be the main thread.
    @param aBuf         Buffer to write the prompt text that will be displayed
                        to the user.
    @param aBufSiz      Size of the buffer in characters.
*/
extern void
MSYS_GetPrompt(
    MSYS_Thread *   aThread,
    TCHAR *         aBuf,
    size_t          aBufSiz
    );

/*! @brief Display an option to the user in the standard way.

    @param aThread      Thread requesting the prompt. If threads have not been
                        enabled, this will always be the main thread.
    @param aOption      Option name
    @param aValue       Option value. This value will be automatically quoted
                        as necessary such that the value will be a valid setopt
                        statement.
*/
extern void
MSYS_DisplayOption(
    MSYS_Thread *   aThread,
    const TCHAR *   aOption,
    const TCHAR *   aValue
    );

/*! @brief Returns if detailed help has been requested in the arguments.

    This function examines the arguments for any instances of "-?" or "/?"
    to determine if detailed help has been requested for this command.
    It will ignore those arguments if they have been suppressed with a
    command prefix.

    @param aThread      Thread as supplied to the command handler.
    @param aHandler     Command handler function pointer.
    @param aArgs        Arguments as supplied to the command handler.
    @param aShowHelp    If true and help has been requested, ShowHelp will
                        be automatically called for this handler before
                        it returns.

    @retval true    User has requested detailed help for this command.
    @retval false   User has not requested detailed help for this command.
*/
extern bool
MSYS_NeedHelp(
    MSYS_Thread *   aThread,
    MSYS_Handler    aHandler,
    MSYS_Args &     aArgs,
    bool            aShowHelp = true
    );

/*! @brief Display summary help or detailed command help.

    @param aThread      Thread as supplied to the command handler.
    @param aHandler     Command handler function pointer to display the
                        help for. If this is NULL then summary help will
                        be displayed. Otherwise the detailed help for that
                        command will be displayed.
*/
extern void
MSYS_ShowHelp(
    MSYS_Thread *   aThread,
    MSYS_Handler    aHandler
    );

/*! @brief Display a string.

    Thread-safe display of text to the console and writing to the tee file
    when it is enabled. Additionally, this function will replace thread
    keywords with the actual thread details when thread handling is enabled.

    The thread keywords that are replaced are:
    <table>
        <tr><td>{{THREADID}}    <td>Numeric thread ID.
        <tr><td>{{THREADNAME}}  <td>Thread name as assigned at thread creation.
    </table>

    @param aThread      Thread as supplied to the command handler.
    @param aFormat      Format string the same as used by printf(). Note that
                        the string character format uses the Windows style of:
                        <table>
                        <tr><th>TCHAR       <th>argument        <th>format spec
                        <tr><td>char        <td>char            <td>%s
                        <tr><td>wchar_t     <td>wchar_t         <td>%s
                        <tr><td>char        <td>wchar_t         <td>%S
                        <tr><td>wchar_t     <td>char            <td>%S
                        </table>
*/
extern void
MSYS_Display(
    MSYS_Thread *   aThread,
    const TCHAR *   aFormat,
    ...
    );

/*! @brief Output to a file. The file should be opened in "wb" mode.

    Thread-safe output of text to the file. Additionally, this function will 
    replace thread keywords with the actual thread details when thread handling 
    is enabled.

    The thread keywords that are replaced are:
    <table>
        <tr><td>{{THREADID}}    <td>Numeric thread ID.
        <tr><td>{{THREADNAME}}  <td>Thread name as assigned at thread creation.
    </table>

    @param aThread      Thread as supplied to the command handler.
    @param aFormat      Format string the same as used by printf(). Note that
                        the string character format uses the Windows style of:
                        <table>
                        <tr><th>TCHAR       <th>argument        <th>format spec
                        <tr><td>char        <td>char            <td>%s
                        <tr><td>wchar_t     <td>wchar_t         <td>%s
                        <tr><td>char        <td>wchar_t         <td>%S
                        <tr><td>wchar_t     <td>char            <td>%S
                        </table>
*/
extern void 
MSYS_OutputToFile(
    MSYS_Thread *   aThread, 
    FILE *          aFile, 
    const TCHAR *   aFormat, 
    ...
    );

/*! @brief Display a string word-wrapped for 80 column display.

    This function will word-wrap text and output it using MSYS_Display.

    @param aThread      Thread as supplied to the command handler.
    @param aText        Text to be displayed.
*/
extern void
MSYS_DisplayWrapped(
    MSYS_Thread *   aThread,
    const TCHAR *   aText
    );

/*! @brief Lookup a Windows error value and display the message.

    @param aThread      Thread requesting the prompt. If threads have not been
                        enabled, this will always be the main thread.
    @param aLocation    Error location to display in the message.
    @param aError       Error value to lookup.
*/
#ifdef _WIN32
extern void
MSYS_DisplayWindowsError(
    MSYS_Thread *   aThread,
    const TCHAR *   aLocation,
    HRESULT         aError
    );
#endif

#endif // INCLUDED_menusys
